/**
 * Copyright (c) [2021-2021] [libbylg@126.com]
 * [lplogger] is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL
 * v2. You may obtain a copy of Mulan PSL v2 at:
 *             http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. See the Mulan PSL v2 for more
 * details.
 */
#include "lp_logger_default.h"

#include "lp_asserts.h"
#include "lp_logger_default.h"

#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

struct lp_sink_localfile_t {
    struct lp_sink_t sink;
    struct lp_formatlist_t* formats;
    struct lp_buffer_t* buffer;
    int8_t flush_level;
    FILE* file;
    char* filename;
};

static void lp_sink_localfile_print(struct lp_sink_t* sink, void* msg);
static void lp_sink_localfile_flush(struct lp_sink_t* sink);
static int lp_sink_localfile_setup(struct lp_sink_t* sink, int8_t attr_id, uintptr_t attr_value);

LPEXTERN struct lp_sink_t* lp_new_sink_localfile(void* context, LP_ATTR_PROVIDER provider) {
    struct lp_sink_localfile_t* console_sink = (struct lp_sink_localfile_t*)malloc(sizeof(struct lp_sink_localfile_t));
    if (console_sink == NULL) {
        ASSERT(false);
        return NULL;
    }

    console_sink->sink.print = lp_sink_localfile_print;
    console_sink->sink.flush = lp_sink_localfile_flush;
    console_sink->sink.setup = lp_sink_localfile_setup;

    console_sink->formats = lp_new_formatlist();
    if (console_sink->formats == NULL) {
        free(console_sink);
        ASSERT(false);
        return NULL;
    }

    console_sink->buffer = lp_new_buffer(LP_BUFFER_CAP_MIN);
    if (console_sink->buffer == NULL) {
        lp_del_formatlist(console_sink->formats);
        free(console_sink);
        ASSERT(false);
        return NULL;
    }

    console_sink->flush_level = LP_LEVEL_ERROR;
    console_sink->file = NULL;
    console_sink->filename = NULL;

    if (provider == NULL) {
        return (struct lp_sink_t*)(console_sink);
    }

    while (true) {
        //  尝试获取下一个属性
        int8_t attr_id = -1;
        uintptr_t attr_value = (uintptr_t)(0);
        if (0 != provider(context, &attr_id, &attr_value)) {
            return (struct lp_sink_t*)(console_sink);
        }

        /////////////
    }

    return (struct lp_sink_t*)(console_sink);
}

LPEXTERN void lp_del_sink_localfile(struct lp_sink_t* sink) {
}

static void lp_sink_localfile_print(struct lp_sink_t* sink, void* msg) {
    ASSERT(sink != NULL);

    struct lp_sink_localfile_t* localfile_sink = (struct lp_sink_localfile_t*)(sink);
    ASSERT(localfile_sink->buffer);
    ASSERT(localfile_sink->formats);

    int ret = lp_log_format(localfile_sink->buffer, localfile_sink->formats, msg);
    ASSERT(ret == 0);

    //  尝试打开指定的文件
    if (localfile_sink->file == NULL) {
        if (localfile_sink->filename == NULL) {
            return;
        }

        localfile_sink->file = fopen(localfile_sink->filename, "a+");
        if (localfile_sink->file == NULL) {
            return;
        }
    }

    fwrite(localfile_sink->buffer->data, localfile_sink->buffer->len, 1, localfile_sink->file);
}

static void lp_sink_localfile_flush(struct lp_sink_t* sink) {
    ASSERT(sink != NULL);

    struct lp_sink_localfile_t* localfile_sink = (struct lp_sink_localfile_t*)(sink);
    fflush(localfile_sink->file);
}

static int lp_sink_localfile_setup(struct lp_sink_t* sink, int8_t attr_id, uintptr_t attr_value) {
    ASSERT(sink != NULL);
    ASSERT(attr_id >= LP_SINK_ATTR_MIN);
    ASSERT((int)(attr_id) < LP_SINK_ATTR_COUNT_MAX);

    struct lp_sink_localfile_t* localfile_sink = (struct lp_sink_localfile_t*)(sink);

    //  设置本 sink 所支持的属性
    switch (attr_id) {
        case LP_SINK_ATTR_PATTERN: {
            const char* pattern = (const char*)(attr_value);
            if (pattern == NULL) {
                ASSERT(false);
                return -1;
            }

            int ret = lp_compile_pattern(pattern, localfile_sink->formats);
            ASSERT(ret == 0);
            return ret;
        } break;
        case LP_SINK_ATTR_FLUSHLEVEL: {
            int8_t level = (int8_t)(attr_value);
            if ((level < LP_LEVEL_MIN) || (level >= LP_LEVEL_DEFAULT_COUNT)) {
                ASSERT(false);
                return -1;
            }

            localfile_sink->flush_level = level;
            return 0;
        } break;
        case LP_SINK_ATTR_LOCALFILE_FILEPATH: {
            if ((const char*)(attr_value) == NULL) {
                ASSERT(false);
                return -1;
            }

            if (localfile_sink->filename != NULL) {
                //  文件没有变化，直接返回
                if (strcmp(localfile_sink->filename, (const char*)(attr_value)) == 0) {
                    return 0;
                }

                //  文件变化了，需要用新文件名代替旧文件名
                char* new_filepath = strdup((const char*)(attr_value));
                if (new_filepath == NULL) {
                    ASSERT(false);
                    return -1;
                }

                //  替换旧文件名
                free(localfile_sink->filename);
                localfile_sink->filename = NULL;
                localfile_sink->filename = new_filepath;

                //  如果之前的文件名，已经更有打开的文件，那么这个文件也要关闭
                if (localfile_sink->file != NULL) {
                    fclose(localfile_sink->file);
                    localfile_sink->file = NULL;
                }
            } else {
                ASSERT(localfile_sink->file == NULL);
                localfile_sink->filename = strdup((const char*)(attr_value));
                if (localfile_sink->filename == NULL) {
                    ASSERT(false);
                    return -1;
                }
            }

            ASSERT(localfile_sink->file == NULL);

            //  尝试打开指定的文件
            localfile_sink->file = fopen(localfile_sink->filename, "a+");
            if (localfile_sink->file == NULL) {
                return -1;
            }

            return 0;
        } break;
        default: {
            //  不支持的属性会被忽略掉
            ASSERT(false);
            return -1;
        } break;
    }

    ASSERT(false);
    return -1;
}
